package com.fight.strive.sys.modules.rbac.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fight.strive.sys.enums.OperateStatusEnum;
import com.fight.strive.sys.enums.OrgTypeEnum;
import com.fight.strive.sys.enums.SexEnum;
import com.fight.strive.sys.modules.exception.StriveException;
import com.fight.strive.sys.modules.mybatisplus.utils.MyBatisPlusUtils;
import com.fight.strive.sys.modules.rbac.constants.RbacUserConstants;
import com.fight.strive.sys.modules.rbac.dao.RbacUserMapper;
import com.fight.strive.sys.modules.rbac.dto.ChangeUserPasswordDto;
import com.fight.strive.sys.modules.rbac.dto.RbacOrgDto;
import com.fight.strive.sys.modules.rbac.dto.RbacUserDto;
import com.fight.strive.sys.modules.rbac.dto.RbacUserOrgDto;
import com.fight.strive.sys.modules.rbac.dto.RbacUserRegDto;
import com.fight.strive.sys.modules.rbac.entity.RbacUserEntity;
import com.fight.strive.sys.modules.rbac.service.RbacOrgRoleService;
import com.fight.strive.sys.modules.rbac.service.RbacOrgService;
import com.fight.strive.sys.modules.rbac.service.RbacUserRoleService;
import com.fight.strive.sys.modules.rbac.service.RbacUserService;
import com.fight.strive.sys.modules.request.dto.PageRequest;
import com.fight.strive.sys.modules.response.dto.PageData;
import com.fight.strive.sys.modules.sysadmin.utils.SysAdminUtils;
import com.fight.strive.sys.utils.BeanUtils;
import com.fight.strive.sys.utils.CollectionUtils;
import com.fight.strive.sys.utils.DigestUtils;
import com.fight.strive.sys.utils.IdCardUtils;
import com.fight.strive.sys.utils.ObjectUtils;
import com.fight.strive.sys.utils.RandomStringUtils;
import com.fight.strive.sys.utils.StringUtils;
import com.fight.strive.sys.utils.ValidateUtils;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.context.annotation.Lazy;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;

/**
 * 用户服务类
 *
 * @author ZHOUXIANG
 */
@Slf4j
@Service
public class RbacUserServiceImpl
        extends ServiceImpl<RbacUserMapper, RbacUserEntity>
        implements RbacUserService {

    @Resource
    @Lazy
    private RbacUserRoleService rbacUserRoleService;

    @Resource
    private RbacOrgRoleService rbacOrgRoleService;

    @Resource
    @Lazy
    private RbacOrgService rbacOrgService;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void registerUser(@Valid RbacUserRegDto regDto) {
        if (!StringUtils.equals(regDto.getPassword(), regDto.getConfirmPassword())) {
            throw new StriveException("两次密码输入不一致");
        }
        RbacUserEntity entity = new RbacUserEntity();
        // 注册的都是租户
        entity.setIsTenant(OperateStatusEnum.Y.name())
                .setLoginName(regDto.getLoginName())
                .setPassword(DigestUtils.md5Hex(regDto.getPassword()));
        try {
            this.saveOrUpdate(entity);
        } catch (DuplicateKeyException e) {
            throw new StriveException("该用户已存在");
        }
        // 生成租户顶层组织
        RbacOrgDto orgDto = new RbacOrgDto();
        orgDto.setOrgCode(regDto.getOrgCode())
                .setOrgName(regDto.getOrgName())
                .setAliasName(regDto.getOrgAliasName())
                .setOrgTypeCode(OrgTypeEnum.COMPANY.name());
        // 租户ID即为当前注册用户的ID
        rbacOrgService.registerOrg(entity.getId(), orgDto);
    }

    @Override
    public void saveUser(@Valid RbacUserDto rbacUserDto) {
        RbacUserEntity rbacUserEntity = new RbacUserEntity();
        // 验证字段
        this.validUserField(rbacUserDto);
        // 复制字段到实体
        BeanUtils.copyPropertiesIgnoreNull(rbacUserDto, rbacUserEntity);
        // 性别中文处理
        rbacUserEntity.setSexName(
                SexEnum.getValueByName(rbacUserDto.getSexCode()));
        // 如果密码为空且是新增则给一个默认密码
        if (StringUtils.isBlank(rbacUserEntity.getPassword())) {
            if (ObjectUtils.isNull(rbacUserEntity.getId())) {
                rbacUserEntity.setPassword(RandomStringUtils.genUUID());
            }
        } else {
            rbacUserEntity.setPassword(
                    DigestUtils.md5Hex(rbacUserEntity.getPassword()));
        }
        try {
            this.saveOrUpdate(rbacUserEntity);
            // 更新 redis 缓存
            this.updateRedisData(rbacUserEntity);
        } catch (DuplicateKeyException e) {
            throw new StriveException("该用户已存在");
        }
    }

    @Override
    public RbacUserEntity getOneByMobileAndInsert(String mobile) {
        RbacUserEntity userEntity = this.getRedisData(
                RbacUserConstants.MOBILE_KEY, mobile);
        if (ObjectUtils.notNull(userEntity)) {
            return userEntity;
        }
        try {
            QueryWrapper<RbacUserEntity> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("mobile", mobile);
            userEntity = this.getOne(queryWrapper);
        } catch (MyBatisSystemException e) {
            throw new StriveException("手机号重复");
        }
        // 如果此手机号的用户不存在则插入
        if (ObjectUtils.isNull(userEntity)) {
            userEntity = new RbacUserEntity();
            userEntity.setLoginName(mobile).setPassword(DigestUtils.md5Hex(mobile.concat("_strive")))
                    .setMobile(mobile).setIsTenant(OperateStatusEnum.N.name());
            this.saveOrUpdate(userEntity);
        }
        // 更新 rbac_user 缓存
        this.updateRedisData(userEntity);
        return userEntity;
    }

    @Override
    public PageData<RbacUserEntity> getUserList(PageRequest<RbacUserDto> pageRequest) {
        RbacUserDto rbacUserDto = pageRequest.getCondition();
        QueryWrapper<RbacUserEntity> queryWrapper = new QueryWrapper<>();
        MyBatisPlusUtils.buildQuery(queryWrapper, pageRequest);
        MyBatisPlusUtils.buildSortOrderQuery(queryWrapper, pageRequest.getSort());
        Page<RbacUserEntity> page = pageRequest.buildMybatisPlusPage();
        IPage<RbacUserEntity> iPage = this.page(page, queryWrapper);
        return new PageData<>(iPage);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteUser(RbacUserDto rbacUserDto) {
        if (ObjectUtils.isNull(rbacUserDto.getId())) {
            throw new StriveException("用户ID不能为空");
        }
        RbacUserEntity entity = this.getUserById(rbacUserDto.getId());
        // 删除缓存
        this.deleteRedisData(entity);
        // 删除数据库
        this.removeById(entity.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteBatchUser(List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            throw new StriveException("请选择用户ID");
        }
        for (Long id : ids) {
            RbacUserEntity entity = this.getUserById(id);
            this.deleteRedisData(entity);
        }
        this.removeByIds(ids);
    }

    @Override
    public Long getTenantIdByUserId(Long userId) {
        Long tenantId = (Long) redisTemplate.opsForHash().get(
                RbacUserConstants.TENANT_ID_KEY, userId);
        if (ObjectUtils.isNull(tenantId)) {
            RbacUserEntity entity = this.getById(userId);
            if (ObjectUtils.notNull(entity)) {
                if (StringUtils.equalsIgnoreCase(
                        entity.getIsTenant(), OperateStatusEnum.Y.name())) {
                    // 当前登录用户是租户自己
                    tenantId = entity.getId();
                } else {
                    tenantId = entity.getTenantId();
                }
            } else {
                tenantId = -1L;
            }
            // 缓存 userId -> tenantId
            redisTemplate.opsForHash().put(
                    RbacUserConstants.TENANT_ID_KEY, userId, tenantId);
        }
        return tenantId;
    }

    @Override
    public RbacUserEntity getUserByLoginName(String loginName) {
        RbacUserEntity entity = this.getRedisData(
                RbacUserConstants.LOGIN_NAME_KEY, loginName);
        if (ObjectUtils.isNull(entity)) {
            QueryWrapper<RbacUserEntity> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("login_name", loginName)
                    .last("limit 1");
            entity = this.baseMapper.getUserByLoginName(queryWrapper);
            if (ObjectUtils.notNull(entity)) {
                this.updateRedisData(entity);
            }
        }
        return entity;
    }

    @Override
    public void changeUsersOrg(RbacUserOrgDto dto) {
        if (CollectionUtils.isEmpty(dto.getUserIds())) {
            throw new StriveException("用户ID列表为空");
        }
        dto.getUserIds().forEach(
                userId -> updateUserOrg(userId, dto.getOrgId()));
    }

    @Override
    public void changeUsersPassword(@Valid ChangeUserPasswordDto dto) {
        List<Long> userIds = dto.getUserIds();
        if (dto.isReset()) {
            if (CollectionUtils.isEmpty(userIds)) {
                throw new StriveException("用户ID不能为空");
            }
            userIds.forEach(
                    userId -> changeUserPassword(userId, dto.getPassword()));
        } else {
            RbacUserEntity entity;
            if (CollectionUtils.isNotEmpty(userIds)) {
                if (userIds.size() > 1) {
                    throw new StriveException("只能选择一个用户");
                } else {
                    entity = this.getById(userIds.get(0));
                }
            } else {
                entity = SysAdminUtils.getCurrentUserEntity();
            }
            if (ObjectUtils.isNull(entity)) {
                throw new StriveException("无法获取用户");
            }
            // 判断原始密码是否正确
            if (StringUtils.equals(
                    DigestUtils.md5Hex(
                            dto.getOriginPassword()), entity.getPassword())) {
                entity.setPassword(DigestUtils.md5Hex(dto.getPassword()));
            } else {
                throw new StriveException("原始密码不正确");
            }
            this.updateById(entity);
            // 更新 redis
            this.updateRedisData(entity);
        }
    }

    @Override
    public void changeTenantPassword(@Valid ChangeUserPasswordDto dto) {
        String md5HexOriginPass = DigestUtils.md5Hex(dto.getOriginPassword());
        RbacUserEntity rbacUserEntity = SysAdminUtils.getCurrentUserEntity();
        if (ObjectUtils.isNull(rbacUserEntity) ||
                !StringUtils.equalsIgnoreCase(md5HexOriginPass,
                        rbacUserEntity.getPassword())) {
            throw new StriveException("原密码不正确");
        }
        if (!StringUtils.equalsIgnoreCase(dto.getPassword(), dto.getConfirmPassword())) {
            throw new StriveException("两次密码输入不一致");
        }
        if (StringUtils.isNotBlank(dto.getLoginName())) {
            rbacUserEntity.setLoginName(dto.getLoginName());
        }
        rbacUserEntity.setPassword(DigestUtils.md5Hex(dto.getPassword()));
        this.baseMapper.updateTenantById(rbacUserEntity);
        this.updateRedisData(rbacUserEntity);
    }

    @Override
    public Long getUserOrgId(Long userId) {
        Long orgId = (Long) redisTemplate.opsForHash().get(
                RbacUserConstants.USER_ORG_ID_KEY, userId);
        if (ObjectUtils.isNull(orgId)) {
            RbacUserEntity entity = this.getUserById(userId);
            if (ObjectUtils.notNull(entity)) {
                orgId = entity.getOrgId();
                this.updateRedisData(entity);
            }
        }
        return orgId;
    }

    @Override
    public RbacUserEntity getUserById(Long userId) {
        RbacUserEntity entity =
                this.getRedisData(RbacUserConstants.ID_KEY, userId);
        if (ObjectUtils.isNull(entity)) {
            entity = this.getById(userId);
            if (ObjectUtils.notNull(entity)) {
                this.updateRedisData(entity);
            }
        }
        return entity;
    }

    /**
     * 统一更新 reids 中保存的数据及关系
     *
     * @param entity 用户实体
     */
    @Override
    public void updateRedisData(RbacUserEntity entity) {
        // userId -> user entity
        redisTemplate.opsForHash().put(
                RbacUserConstants.ID_KEY, entity.getId(), entity);
        // login name -> user id
        redisTemplate.opsForHash().put(RbacUserConstants.LOGIN_NAME_KEY,
                entity.getLoginName(), entity.getId());
        if (ObjectUtils.notNull(entity.getMobile())) {
            // mobile -> userId
            redisTemplate.opsForHash().put(RbacUserConstants.MOBILE_KEY,
                    entity.getMobile(), entity.getId());
        }
        // userId -> orgId
        redisTemplate.opsForHash().put(RbacUserConstants.USER_ORG_ID_KEY,
                entity.getId(), entity.getOrgId());
    }

    /**
     * 从redis 获取数据
     *
     * @param key     redis key
     * @param hashKey hash key
     * @return rbac user entity
     */
    @Override
    public RbacUserEntity getRedisData(String key, Object hashKey) {
        if (key.equals(RbacUserConstants.ID_KEY)) {
            return (RbacUserEntity) redisTemplate.opsForHash().get(key, hashKey);
        } else {
            Long userId = (Long) redisTemplate.opsForHash().get(key, hashKey);
            if (ObjectUtils.notNull(userId)) {
                return (RbacUserEntity) redisTemplate.opsForHash().get(
                        RbacUserConstants.ID_KEY, userId);
            }
        }
        return null;
    }

    /**
     * 删除该用户所有缓存数据
     *
     * @param entity 用户实体
     */
    @Override
    public void deleteRedisData(RbacUserEntity entity) {
        redisTemplate.opsForHash().delete(
                RbacUserConstants.ID_KEY, entity.getId());
        redisTemplate.opsForHash().delete(
                RbacUserConstants.LOGIN_NAME_KEY, entity.getLoginName());
        redisTemplate.opsForHash().delete(
                RbacUserConstants.MOBILE_KEY, entity.getMobile());
        redisTemplate.opsForHash().delete(
                RbacUserConstants.TENANT_ID_KEY, entity.getId());
        redisTemplate.opsForHash().delete(
                RbacUserConstants.USER_ORG_ID_KEY, entity.getId());
    }

    /**
     * 验证用户字段的正确性
     *
     * @param dto 用户字段
     */
    private void validUserField(RbacUserDto dto) {
        // 验证手机号的正确性
        if (StringUtils.isNotBlank(dto.getMobile())) {
            if (!ValidateUtils.isMobile(dto.getMobile())) {
                throw new StriveException("手机号格式不正确");
            }
        }
        // 验证IP的正确性
        if (StringUtils.isNotBlank(dto.getLimitIp())) {
            if (!ValidateUtils.isIPAddr(dto.getLimitIp())) {
                throw new StriveException("IP格式不正确");
            }
        }
        // 验证身份证号的正确性
        if (StringUtils.isNotBlank(dto.getCardId())) {
            if (!IdCardUtils.validateCard(dto.getCardId())) {
                throw new StriveException("身份证号格式不正确");
            }
        }
        // 验证email的正确性
        if (StringUtils.isNotBlank(dto.getEmail())) {
            if (!ValidateUtils.isEmail(dto.getEmail())) {
                throw new StriveException("email格式不正确");
            }
        }
    }

    /**
     * 更新用户的组织ID
     *
     * @param userId 用户ID
     * @param orgId  组织ID
     */
    private void updateUserOrg(Long userId, Long orgId) {
        RbacUserEntity entity = this.getById(userId);
        entity.setId(userId);
        entity.setOrgId(orgId);
        this.updateById(entity);
        // 更新缓存
        this.updateRedisData(entity);
    }

    /**
     * 重置单个用户的密码
     *
     * @param userId   用户ID
     * @param password 用户密码
     */
    private void changeUserPassword(Long userId, String password) {
        RbacUserEntity entity = this.getById(userId);
        entity.setPassword(DigestUtils.md5Hex(password));
        this.updateById(entity);
        this.updateRedisData(entity);
    }
}
